/*
 * Copyright 2006-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * Copyright 2007 MartiniMix.net
 */
package net.martinimix.logging;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.bluemartini.dna.BMContext;
import com.bluemartini.dna.BMLog;
import com.bluemartini.dna.DNAList;
import com.bluemartini.dna.DNAListIterator;

/**
 * Provides a Blue Martini log implementation of 
 * Apache Common's Logging Framework.
 * 
 * @author Scott Rossillo
 *
 */
public class BlueMartiniLog implements Log {
	
	private int componentId = 0;
	private boolean initialized = false;
	
	private final String logName;
	private LogFactory logFactory = null;
	
	/**
	 * Constructs a new log for this given log name.
	 * 
	 * @param name the name of this <code>Log</code>
	 */
	protected BlueMartiniLog(String name) {
		this.logName= name;
	}
	
	/**
	 * Recursively locates the logger for the given name.
	 * 
	 * @param components the <code>DNAList</code> containing log components
	 * @param componentName the name of the component to locate
	 * 
	 * @return the name of the logger for the given <code>name</code>
	 */
	private String findComponent(DNAList components, String componentName) {
		
		DNAListIterator it = components.insertionIterator();
		
		if(BlueMartiniLogFactory.DEBUG_INTERNAL_ENABLED) {
			System.err.println("Finding component " + componentName);
		}
		// loop through them and try to find the best component match
		while(it.hasNext()) {
			
			String component = it.nextName();
			if(component.equalsIgnoreCase(componentName)) {
				if(BlueMartiniLogFactory.DEBUG_INTERNAL_ENABLED) {
					System.err.println("Found " + componentName);
				}
				return componentName;
			}
		}
		
		// try shortening the name and searching again
		int index = componentName.lastIndexOf('.');
		if(index > 0) {
			return findComponent(components, componentName.substring(0, index));
		}
		
		return null;
	}
	
	/**
	 * Initializes this log.
	 */
	protected void initLog() {
		
		if(initialized) {
			return;
		}
		
		if(BlueMartiniLogFactory.DEBUG_INTERNAL_ENABLED) {
			System.err.println("Initializing log " + logName);
		}
		
		// use app config to chose a component name
		if(!LogStartupCallback.isInitialiazed()) {
			throw new IllegalStateException("Logging must be initialized first!");
		}
		
		DNAList logConfig = BMContext.getAppConfig().getList("log");
		final DNAList components;
		String componentName = null;
		
		if(logConfig == null) {
			logConfig = new DNAList();
			throw new AssertionError("Log configuration section is required!");
		}

		components = logConfig.getList("components");

		if(components != null) {
			componentName = findComponent(components, logName);
		}

		if(componentName == null) {
			componentName = logConfig.getString("name", "default");
		}

		this.setComponentId(BMLog.addComponent(componentName));
		
		if(BlueMartiniLogFactory.DEBUG_INTERNAL_ENABLED) {
			System.err.println(
					"Initialized log " + logName + " to " + componentName + " id " + componentId);
		}
		
		initialized = true;
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#debug(java.lang.Object)
	 */
	public void debug(Object o) {
		debug(o, null);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#debug(java.lang.Object, java.lang.Throwable)
	 */
	public void debug(Object o, Throwable t) {
		log(BMLog.LEVEL_DEBUG, o, t);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#error(java.lang.Object)
	 */
	public void error(Object o) {
		error(o, null);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#error(java.lang.Object, java.lang.Throwable)
	 */
	public void error(Object o, Throwable t) {
		log(BMLog.LEVEL_ERROR, o, t);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#fatal(java.lang.Object)
	 */
	public void fatal(Object o) {
		fatal(o, null);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#fatal(java.lang.Object, java.lang.Throwable)
	 */
	public void fatal(Object o, Throwable t) {
		log(BMLog.LEVEL_CRITICAL, o, t);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#info(java.lang.Object)
	 */
	public void info(Object o) {
		info(o, null);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#info(java.lang.Object, java.lang.Throwable)
	 */
	public void info(Object o, Throwable t) {
		log(BMLog.LEVEL_INFO, o, t);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#isDebugEnabled()
	 */
	public boolean isDebugEnabled() {
		return BMLog.logEnabled(componentId, BMLog.LEVEL_DEBUG);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#isErrorEnabled()
	 */
	public boolean isErrorEnabled() {
		return BMLog.logEnabled(componentId, BMLog.LEVEL_ERROR);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#isFatalEnabled()
	 */
	public boolean isFatalEnabled() {
		return BMLog.logEnabled(componentId, BMLog.LEVEL_CRITICAL);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#isInfoEnabled()
	 */
	public boolean isInfoEnabled() {
		return BMLog.logEnabled(componentId, BMLog.LEVEL_INFO);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#isTraceEnabled()
	 */
	public boolean isTraceEnabled() {
		return BMLog.logEnabled(componentId, BMLog.LEVEL_TRACE);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#isWarnEnabled()
	 */
	public boolean isWarnEnabled() {
		return BMLog.logEnabled(componentId, BMLog.LEVEL_WARN);
	}
	
	/**
	 * Logs the given object and exception.
	 * 
	 * @param level the log level for the log entry
	 * @param o the <code>Object</code> to be logged
	 * @param ex the <code>Throwable</code> to be logged
	 */
	private void log(int level, Object o, Throwable ex) {
		
		if(!BMLog.logEnabled(componentId, level)) {
			return;
		}
		
		final Throwable t = ex != null ? ex : new Throwable();
		final String message =  o != null ? o.toString() : null;	
		final StackTraceElement[] stackTrace = t.getStackTrace();
		
		String className = null;
		String methodName = null;
		
		if(stackTrace != null) {
			
			for(int i = 0; i < stackTrace.length; i++) {
				
				StackTraceElement e = stackTrace[i];
				className = e.getClassName();
				methodName = e.getMethodName();
				
				if(!className.equals(BlueMartiniLog.class.getName())) {	
					
					StringBuffer b = new StringBuffer(className);
					
					b.append('(');
					b.append(e.getFileName());
					b.append(':');
					b.append(e.getLineNumber());
					b.append(')');
					
					className = b.toString(); 
					break;
				}
			}
		}
		
		BMLog.log(componentId, level, className, methodName, message, ex);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#trace(java.lang.Object)
	 */
	public void trace(Object o) { 
		trace(o, null);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#trace(java.lang.Object, java.lang.Throwable)
	 */
	public void trace(Object o, Throwable t) { 
		log(BMLog.LEVEL_TRACE, o, t);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#warn(java.lang.Object)
	 */
	public void warn(Object o) {
		warn(o, null);
	}

	/* (non-Javadoc)
	 * @see org.apache.commons.logging.Log#warn(java.lang.Object, java.lang.Throwable)
	 */
	public void warn(Object o, Throwable t) {
		log(BMLog.LEVEL_WARN, o, t);
	}

	/**
	 * Returns this log's component identifier.
	 * 
	 * @return the coponent id for this <code>Log</code> 
	 * 
	 */
	public int getComponentId() {
		return componentId;
	}

	/**
	 * Sets the component identifier for this log.
	 * 
	 * @param componentId for id for this <code>Log</code>
	 * 
	 */
	protected void setComponentId(int componentId) {
		this.componentId = componentId;
	}

	/**
	 * Returns the log factory for this log.
	 * 
	 * @return the <code>LogFactory<code> for this <code>Log</code>
	 */
	public LogFactory getLogFactory() {
		return logFactory;
	}

	/**
	 * Sets the log factory for this log.
	 * 
	 * @param logFactory the <code>LogFactory</code> for
	 * this <code>Log</code>
	 */
	public void setLogFactory(LogFactory logFactory) {
		this.logFactory = logFactory;
	}

}
